// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/cache_storage/cache_storage_dispatcher_host.h"

#include <stddef.h>
#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/bad_message.h"
#include "content/browser/cache_storage/cache_storage_cache.h"
#include "content/browser/cache_storage/cache_storage_cache_handle.h"
#include "content/browser/cache_storage/cache_storage_context_impl.h"
#include "content/browser/cache_storage/cache_storage_manager.h"
#include "content/common/cache_storage/cache_storage_messages.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/origin_util.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/WebServiceWorkerCacheError.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {

namespace {

    const uint32_t kFilteredMessageClasses[] = { CacheStorageMsgStart };
    const int32_t kCachePreservationSeconds = 5;

    blink::WebServiceWorkerCacheError ToWebServiceWorkerCacheError(
        CacheStorageError err)
    {
        switch (err) {
        case CACHE_STORAGE_OK:
            NOTREACHED();
            return blink::WebServiceWorkerCacheErrorNotImplemented;
        case CACHE_STORAGE_ERROR_EXISTS:
            return blink::WebServiceWorkerCacheErrorExists;
        case CACHE_STORAGE_ERROR_STORAGE:
            // TODO(nhiroki): Add WebServiceWorkerCacheError equivalent to
            // CACHE_STORAGE_ERROR_STORAGE.
            return blink::WebServiceWorkerCacheErrorNotFound;
        case CACHE_STORAGE_ERROR_NOT_FOUND:
            return blink::WebServiceWorkerCacheErrorNotFound;
        case CACHE_STORAGE_ERROR_QUOTA_EXCEEDED:
            return blink::WebServiceWorkerCacheErrorQuotaExceeded;
        case CACHE_STORAGE_ERROR_CACHE_NAME_NOT_FOUND:
            return blink::WebServiceWorkerCacheErrorCacheNameNotFound;
        case CACHE_STORAGE_ERROR_QUERY_TOO_LARGE:
            return blink::WebServiceWorkerCacheErrorTooLarge;
        }
        NOTREACHED();
        return blink::WebServiceWorkerCacheErrorNotImplemented;
    }

    bool OriginCanAccessCacheStorage(const url::Origin& origin)
    {
        return !origin.unique() && IsOriginSecure(origin.GetURL());
    }

    void StopPreservingCache(
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) { }

} // namespace

CacheStorageDispatcherHost::CacheStorageDispatcherHost()
    : BrowserMessageFilter(kFilteredMessageClasses,
        arraysize(kFilteredMessageClasses))
{
}

CacheStorageDispatcherHost::~CacheStorageDispatcherHost()
{
}

void CacheStorageDispatcherHost::Init(CacheStorageContextImpl* context)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&CacheStorageDispatcherHost::CreateCacheListener, this,
            base::RetainedRef(context)));
}

void CacheStorageDispatcherHost::OnDestruct() const
{
    BrowserThread::DeleteOnIOThread::Destruct(this);
}

bool CacheStorageDispatcherHost::OnMessageReceived(
    const IPC::Message& message)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(CacheStorageDispatcherHost, message)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheStorageHas, OnCacheStorageHas)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheStorageOpen, OnCacheStorageOpen)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheStorageDelete,
        OnCacheStorageDelete)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheStorageKeys, OnCacheStorageKeys)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheStorageMatch,
        OnCacheStorageMatch)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheMatch, OnCacheMatch)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheMatchAll, OnCacheMatchAll)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheKeys, OnCacheKeys)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheBatch, OnCacheBatch)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_CacheClosed, OnCacheClosed)
    IPC_MESSAGE_HANDLER(CacheStorageHostMsg_BlobDataHandled, OnBlobDataHandled)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    if (!handled)
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_NOT_RECOGNIZED);
    return handled;
}

void CacheStorageDispatcherHost::CreateCacheListener(
    CacheStorageContextImpl* context)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    context_ = context;
}

void CacheStorageDispatcherHost::OnCacheStorageHas(
    int thread_id,
    int request_id,
    const url::Origin& origin,
    const base::string16& cache_name)
{
    TRACE_EVENT0("CacheStorage", "CacheStorageDispatcherHost::OnCacheStorageHas");
    if (!OriginCanAccessCacheStorage(origin)) {
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_INVALID_ORIGIN);
        return;
    }
    context_->cache_manager()->HasCache(
        origin.GetURL(), base::UTF16ToUTF8(cache_name),
        base::Bind(&CacheStorageDispatcherHost::OnCacheStorageHasCallback, this,
            thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageOpen(
    int thread_id,
    int request_id,
    const url::Origin& origin,
    const base::string16& cache_name)
{
    TRACE_EVENT0("CacheStorage",
        "CacheStorageDispatcherHost::OnCacheStorageOpen");
    if (!OriginCanAccessCacheStorage(origin)) {
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_INVALID_ORIGIN);
        return;
    }
    context_->cache_manager()->OpenCache(
        origin.GetURL(), base::UTF16ToUTF8(cache_name),
        base::Bind(&CacheStorageDispatcherHost::OnCacheStorageOpenCallback, this,
            thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageDelete(
    int thread_id,
    int request_id,
    const url::Origin& origin,
    const base::string16& cache_name)
{
    TRACE_EVENT0("CacheStorage",
        "CacheStorageDispatcherHost::OnCacheStorageDelete");
    if (!OriginCanAccessCacheStorage(origin)) {
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_INVALID_ORIGIN);
        return;
    }
    context_->cache_manager()->DeleteCache(
        origin.GetURL(), base::UTF16ToUTF8(cache_name),
        base::Bind(&CacheStorageDispatcherHost::OnCacheStorageDeleteCallback,
            this, thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageKeys(int thread_id,
    int request_id,
    const url::Origin& origin)
{
    TRACE_EVENT0("CacheStorage",
        "CacheStorageDispatcherHost::OnCacheStorageKeys");
    if (!OriginCanAccessCacheStorage(origin)) {
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_INVALID_ORIGIN);
        return;
    }
    context_->cache_manager()->EnumerateCaches(
        origin.GetURL(),
        base::Bind(&CacheStorageDispatcherHost::OnCacheStorageKeysCallback, this,
            thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageMatch(
    int thread_id,
    int request_id,
    const url::Origin& origin,
    const ServiceWorkerFetchRequest& request,
    const CacheStorageCacheQueryParams& match_params)
{
    TRACE_EVENT0("CacheStorage",
        "CacheStorageDispatcherHost::OnCacheStorageMatch");
    if (!OriginCanAccessCacheStorage(origin)) {
        bad_message::ReceivedBadMessage(this, bad_message::CSDH_INVALID_ORIGIN);
        return;
    }
    std::unique_ptr<ServiceWorkerFetchRequest> scoped_request(
        new ServiceWorkerFetchRequest(request.url, request.method,
            request.headers, request.referrer,
            request.is_reload));

    if (match_params.cache_name.is_null()) {
        context_->cache_manager()->MatchAllCaches(
            origin.GetURL(), std::move(scoped_request), match_params,
            base::Bind(&CacheStorageDispatcherHost::OnCacheStorageMatchCallback,
                this, thread_id, request_id));
        return;
    }
    context_->cache_manager()->MatchCache(
        origin.GetURL(), base::UTF16ToUTF8(match_params.cache_name.string()),
        std::move(scoped_request), match_params,
        base::Bind(&CacheStorageDispatcherHost::OnCacheStorageMatchCallback, this,
            thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheMatch(
    int thread_id,
    int request_id,
    int cache_id,
    const ServiceWorkerFetchRequest& request,
    const CacheStorageCacheQueryParams& match_params)
{
    IDToCacheMap::iterator it = id_to_cache_map_.find(cache_id);
    if (it == id_to_cache_map_.end() || !it->second->value()) {
        Send(new CacheStorageMsg_CacheMatchError(
            thread_id, request_id, blink::WebServiceWorkerCacheErrorNotFound));
        return;
    }

    CacheStorageCache* cache = it->second->value();
    std::unique_ptr<ServiceWorkerFetchRequest> scoped_request(
        new ServiceWorkerFetchRequest(request.url, request.method,
            request.headers, request.referrer,
            request.is_reload));
    cache->Match(
        std::move(scoped_request), match_params,
        base::Bind(&CacheStorageDispatcherHost::OnCacheMatchCallback, this,
            thread_id, request_id, base::Passed(it->second->Clone())));
}

void CacheStorageDispatcherHost::OnCacheMatchAll(
    int thread_id,
    int request_id,
    int cache_id,
    const ServiceWorkerFetchRequest& request,
    const CacheStorageCacheQueryParams& match_params)
{
    IDToCacheMap::iterator it = id_to_cache_map_.find(cache_id);
    if (it == id_to_cache_map_.end() || !it->second->value()) {
        Send(new CacheStorageMsg_CacheMatchError(
            thread_id, request_id, blink::WebServiceWorkerCacheErrorNotFound));
        return;
    }

    CacheStorageCache* cache = it->second->value();
    if (request.url.is_empty()) {
        cache->MatchAll(
            std::unique_ptr<ServiceWorkerFetchRequest>(), match_params,
            base::Bind(&CacheStorageDispatcherHost::OnCacheMatchAllCallback, this,
                thread_id, request_id, base::Passed(it->second->Clone())));
        return;
    }

    std::unique_ptr<ServiceWorkerFetchRequest> scoped_request(
        new ServiceWorkerFetchRequest(request.url, request.method,
            request.headers, request.referrer,
            request.is_reload));
    if (match_params.ignore_search) {
        cache->MatchAll(
            std::move(scoped_request), match_params,
            base::Bind(&CacheStorageDispatcherHost::OnCacheMatchAllCallback, this,
                thread_id, request_id, base::Passed(it->second->Clone())));
        return;
    }
    cache->Match(
        std::move(scoped_request), match_params,
        base::Bind(&CacheStorageDispatcherHost::OnCacheMatchAllCallbackAdapter,
            this, thread_id, request_id,
            base::Passed(it->second->Clone())));
}

void CacheStorageDispatcherHost::OnCacheKeys(
    int thread_id,
    int request_id,
    int cache_id,
    const ServiceWorkerFetchRequest& request,
    const CacheStorageCacheQueryParams& match_params)
{
    IDToCacheMap::iterator it = id_to_cache_map_.find(cache_id);
    if (it == id_to_cache_map_.end() || !it->second->value()) {
        Send(new CacheStorageMsg_CacheKeysError(
            thread_id, request_id, blink::WebServiceWorkerCacheErrorNotFound));
        return;
    }

    CacheStorageCache* cache = it->second->value();
    std::unique_ptr<ServiceWorkerFetchRequest> request_ptr(
        new ServiceWorkerFetchRequest(request.url, request.method,
            request.headers, request.referrer,
            request.is_reload));
    cache->Keys(
        std::move(request_ptr), match_params,
        base::Bind(&CacheStorageDispatcherHost::OnCacheKeysCallback, this,
            thread_id, request_id, base::Passed(it->second->Clone())));
}

void CacheStorageDispatcherHost::OnCacheBatch(
    int thread_id,
    int request_id,
    int cache_id,
    const std::vector<CacheStorageBatchOperation>& operations)
{
    IDToCacheMap::iterator it = id_to_cache_map_.find(cache_id);
    if (it == id_to_cache_map_.end() || !it->second->value()) {
        Send(new CacheStorageMsg_CacheBatchError(
            thread_id, request_id, blink::WebServiceWorkerCacheErrorNotFound));
        return;
    }

    CacheStorageCache* cache = it->second->value();
    cache->BatchOperation(
        operations,
        base::Bind(&CacheStorageDispatcherHost::OnCacheBatchCallback, this,
            thread_id, request_id, base::Passed(it->second->Clone())));
}

void CacheStorageDispatcherHost::OnCacheClosed(int cache_id)
{
    DropCacheReference(cache_id);
}

void CacheStorageDispatcherHost::OnBlobDataHandled(const std::string& uuid)
{
    DropBlobDataHandle(uuid);
}

void CacheStorageDispatcherHost::OnCacheStorageHasCallback(
    int thread_id,
    int request_id,
    bool has_cache,
    CacheStorageError error)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheStorageHasError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }
    if (!has_cache) {
        Send(new CacheStorageMsg_CacheStorageHasError(
            thread_id, request_id, blink::WebServiceWorkerCacheErrorNotFound));
        return;
    }
    Send(new CacheStorageMsg_CacheStorageHasSuccess(thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageOpenCallback(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheStorageOpenError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    // Hang on to the cache for a few seconds. This way if the user quickly closes
    // and reopens it the cache backend won't have to be reinitialized.
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&StopPreservingCache, base::Passed(cache_handle->Clone())),
        base::TimeDelta::FromSeconds(kCachePreservationSeconds));

    CacheID cache_id = StoreCacheReference(std::move(cache_handle));
    Send(new CacheStorageMsg_CacheStorageOpenSuccess(thread_id, request_id,
        cache_id));
}

void CacheStorageDispatcherHost::OnCacheStorageDeleteCallback(
    int thread_id,
    int request_id,
    bool deleted,
    CacheStorageError error)
{
    if (!deleted || error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheStorageDeleteError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }
    Send(new CacheStorageMsg_CacheStorageDeleteSuccess(thread_id, request_id));
}

void CacheStorageDispatcherHost::OnCacheStorageKeysCallback(
    int thread_id,
    int request_id,
    const CacheStorageIndex& cache_index)
{
    std::vector<base::string16> string16s;
    for (const auto& metadata : cache_index.ordered_cache_metadata())
        string16s.push_back(base::UTF8ToUTF16(metadata.name));
    Send(new CacheStorageMsg_CacheStorageKeysSuccess(thread_id, request_id,
        string16s));
}

void CacheStorageDispatcherHost::OnCacheStorageMatchCallback(
    int thread_id,
    int request_id,
    CacheStorageError error,
    std::unique_ptr<ServiceWorkerResponse> response,
    std::unique_ptr<storage::BlobDataHandle> blob_data_handle)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheStorageMatchError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    if (blob_data_handle)
        StoreBlobDataHandle(*blob_data_handle);

    Send(new CacheStorageMsg_CacheStorageMatchSuccess(thread_id, request_id,
        *response));
}

void CacheStorageDispatcherHost::OnCacheMatchCallback(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error,
    std::unique_ptr<ServiceWorkerResponse> response,
    std::unique_ptr<storage::BlobDataHandle> blob_data_handle)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheMatchError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    if (blob_data_handle)
        StoreBlobDataHandle(*blob_data_handle);

    Send(new CacheStorageMsg_CacheMatchSuccess(thread_id, request_id, *response));
}

void CacheStorageDispatcherHost::OnCacheMatchAllCallbackAdapter(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error,
    std::unique_ptr<ServiceWorkerResponse> response,
    std::unique_ptr<storage::BlobDataHandle> blob_data_handle)
{
    std::unique_ptr<CacheStorageCache::Responses> responses(
        new CacheStorageCache::Responses);
    std::unique_ptr<CacheStorageCache::BlobDataHandles> blob_data_handles(
        new CacheStorageCache::BlobDataHandles);
    if (error == CACHE_STORAGE_OK) {
        DCHECK(response);
        responses->push_back(*response);
        if (blob_data_handle)
            blob_data_handles->push_back(std::move(blob_data_handle));
    }
    OnCacheMatchAllCallback(thread_id, request_id, std::move(cache_handle), error,
        std::move(responses), std::move(blob_data_handles));
}

void CacheStorageDispatcherHost::OnCacheMatchAllCallback(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error,
    std::unique_ptr<CacheStorageCache::Responses> responses,
    std::unique_ptr<CacheStorageCache::BlobDataHandles> blob_data_handles)
{
    if (error != CACHE_STORAGE_OK && error != CACHE_STORAGE_ERROR_NOT_FOUND) {
        Send(new CacheStorageMsg_CacheMatchAllError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    for (const auto& handle : *blob_data_handles) {
        if (handle)
            StoreBlobDataHandle(*handle);
    }

    Send(new CacheStorageMsg_CacheMatchAllSuccess(thread_id, request_id,
        *responses));
}

void CacheStorageDispatcherHost::OnCacheKeysCallback(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error,
    std::unique_ptr<CacheStorageCache::Requests> requests)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheKeysError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    Send(new CacheStorageMsg_CacheKeysSuccess(thread_id, request_id, *requests));
}

void CacheStorageDispatcherHost::OnCacheBatchCallback(
    int thread_id,
    int request_id,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheStorageError error)
{
    if (error != CACHE_STORAGE_OK) {
        Send(new CacheStorageMsg_CacheBatchError(
            thread_id, request_id, ToWebServiceWorkerCacheError(error)));
        return;
    }

    Send(new CacheStorageMsg_CacheBatchSuccess(thread_id, request_id));
}

CacheStorageDispatcherHost::CacheID
CacheStorageDispatcherHost::StoreCacheReference(
    std::unique_ptr<CacheStorageCacheHandle> cache_handle)
{
    int cache_id = next_cache_id_++;
    id_to_cache_map_[cache_id] = std::move(cache_handle);
    return cache_id;
}

void CacheStorageDispatcherHost::DropCacheReference(CacheID cache_id)
{
    id_to_cache_map_.erase(cache_id);
}

void CacheStorageDispatcherHost::StoreBlobDataHandle(
    const storage::BlobDataHandle& blob_data_handle)
{
    std::pair<UUIDToBlobDataHandleList::iterator, bool> rv = blob_handle_store_.insert(std::make_pair(
        blob_data_handle.uuid(), std::list<storage::BlobDataHandle>()));
    rv.first->second.push_front(storage::BlobDataHandle(blob_data_handle));
}

void CacheStorageDispatcherHost::DropBlobDataHandle(const std::string& uuid)
{
    UUIDToBlobDataHandleList::iterator it = blob_handle_store_.find(uuid);
    if (it == blob_handle_store_.end())
        return;
    DCHECK(!it->second.empty());
    it->second.pop_front();
    if (it->second.empty())
        blob_handle_store_.erase(it);
}

} // namespace content
